﻿using LdCommunication.ModBus;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LD.Host.项目
{
    /// <summary>
    /// 电批帮助类
    /// 1.根据控制类型来判断控制方式，默认通讯控制
    /// 2.
    /// </summary>
    public class ScrewHelper
    {
        private ModbusRtu _mdRtu;
        public delegate void ErrorLog(string log);
        public delegate void delNotifying();
        public delegate void delReceiveData(short[] data);
        public event ErrorLog ErrorEvent;
        public event delNotifying StartedEvent;
        public event delNotifying CompletedEvent;
        public event delReceiveData Chn1DataEvent;
        public event delReceiveData Chn2DataEvent;
        public event delReceiveData CompletedDataEvent;
        private const ushort FIX_READ_LEN = 100;            //读取的最大长度
        private const ushort CHN1_ADR = 10000;              //通道1起始地址                         
        private const ushort CHN2_ADR = 10600;              //通道2起始地址
        private ScrewCommType commType = ScrewCommType.PC;  //控制方式
        private short busyDelay = 50;
        private int monitorBusyCount = 2;                   //监控busy失败的次数
        public ScrewHelper(ModbusRtu mbRtu)
        {
            _mdRtu = mbRtu;
        }
        /// <summary>
        /// 延迟监控BUSY信号(ms)
        /// </summary>
        public short BusyDelay
        {
            get => busyDelay;
            set => busyDelay = value;
        }
        /// <summary>
        /// 电批初始化
        /// </summary>
        public void Init()
        {
            short chn1 = 16;
            short chn2 = 128;
            _mdRtu.Write((int)ScrewAdr.CHN1_ENABLED + "", chn1);      //通道1使能
            _mdRtu.Write((int)ScrewAdr.CHN2_ENABLED + "", chn2);      //通道2使能

            ChangeControlType(ScrewCommType.PC);
        }
        public Task Start()
        {
            return Task.Run(() =>
            {
                if (!_mdRtu.IsOpen())
                {
                    ErrorEvent?.Invoke($"Modbus未连接");
                    return;
                }
                bool isRun = true;
                if (commType == ScrewCommType.PC)
                {
                    //电批启动&&BUSY
                    isRun = ScrewDownStart();
                }
                if (isRun)
                {
                    if (CheckBusy())
                    {
                        StartedEvent?.Invoke();

                        DoWork();
                    }
                    else
                    {
                        ErrorEvent?.Invoke($"未监控到BUSY信号【{busyDelay}ms】");
                    }
                }

            }).ContinueWith(Completed);
        }
        private bool CheckBusy()
        {
            bool isBusy = false;
            for (int i = 0; i < monitorBusyCount; i++)
            {
                //延迟50ms监控BUSY
                Task.Delay(busyDelay).Wait();
                isBusy = IoState_Out(0);
                if (isBusy)
                {
                    return isBusy;
                }
                else
                {
                    //关闭信号
                    //再次发送
                    ScrewDownStop();
                    ScrewDownStart();
                }
            }
            return isBusy;
        }
        /// <summary>
        /// 获取数据
        /// </summary>
        private void DoWork()
        {
            short chn1 = _mdRtu.ReadInt16((int)ScrewAdr.CHN1_ENABLED + "").Content;      //通道1使能
            short chn2 = _mdRtu.ReadInt16((int)ScrewAdr.CHN2_ENABLED + "").Content;      //通道2使能
            short readPointCount = 0;
            short lastReadPointCount = 0;
            ushort startAdr = CHN1_ADR;

            while (_mdRtu.IsOpen())
            {
                //获取点数
                readPointCount = _mdRtu.ReadInt16((int)ScrewAdr.POINT_DATA + "").Content;
                if (lastReadPointCount == readPointCount)
                {
                    break;
                }
                /*解析数据算法
                 * 1.计算100倍数读多少次？
                 * 2.超过10600再次循环
                 */
                var readCount = splitCount(startAdr, readPointCount - lastReadPointCount);

                for (int i = 0; i < readCount.Count; i++)
                {
                    //通道1
                    if (chn1 > 0)
                    {
                        var opReadInt16 = _mdRtu.ReadInt16($"{readCount[i][0]}", readCount[i][1]);
                        if (opReadInt16.IsSuccess)
                        {
                            Chn1DataEvent?.Invoke(opReadInt16.Content);
                        }
                    }
                    //通道2
                    if (chn2 > 0)
                    {
                        var opReadInt16 = _mdRtu.ReadInt16($"{readCount[i][0] + 600}", readCount[i][1]);
                        if (opReadInt16.IsSuccess)
                        {
                            Chn2DataEvent?.Invoke(opReadInt16.Content);
                        }
                    }

                    //记录读取最后的地址，下次点位变更需要继续读取
                    startAdr = (ushort)(readCount.Last()[0] + readCount.Last()[1]);
                }

                lastReadPointCount = readPointCount;

                //读完后延迟6ms，再次循环检查是否还有数据递增，
                //最少3ms，因为电批是3ms写入一个地址
                Task.Delay(6).Wait();
            }
        }
        /// <summary>
        /// 计算读取地址和长度
        /// </summary>
        /// <param name="startAdr">起始地址</param>
        /// <param name="count">数据长度</param>
        /// <returns>地址&&长度</returns>
        private List<ushort[]> splitCount(int startAdr, int count)
        {
            List<ushort[]> split = new List<ushort[]>();
            ushort _stAdr = (ushort)startAdr;
            int readCount = (count) / FIX_READ_LEN;
            int mod = count % FIX_READ_LEN;
            for (int i = 0; i < readCount; i++)
            {
                //计算长度是否超过10600，如有则分开
                ushort len = (ushort)(_stAdr + FIX_READ_LEN);
                if (len >= CHN2_ADR)
                {
                    //拆分成2个,第二个从10000开始
                    split.Add(new ushort[2] { _stAdr, (ushort)(CHN2_ADR - _stAdr) });
                    ushort surplus = (ushort)(FIX_READ_LEN - (CHN2_ADR - _stAdr));
                    _stAdr = CHN1_ADR;
                    split.Add(new ushort[2] { _stAdr, surplus });
                    _stAdr += surplus;
                }
                else
                {
                    split.Add(new ushort[2] { _stAdr, (ushort)FIX_READ_LEN });
                    _stAdr = len;
                }
            }
            if (mod > 0)
            {
                //计算长度是否超过10600，如有则分开
                int len = _stAdr + mod;
                if (len >= CHN2_ADR)
                {
                    //拆分成2个,第二个从10000开始
                    split.Add(new ushort[2] { _stAdr, (ushort)(CHN2_ADR - _stAdr) });
                    ushort surplus = (ushort)(FIX_READ_LEN - (CHN2_ADR - _stAdr));
                    _stAdr = CHN1_ADR;
                    split.Add(new ushort[2] { _stAdr, surplus });
                    _stAdr += surplus;
                }
                else
                    split.Add(new ushort[2] { _stAdr, (ushort)mod });
            }

            return split;
        }
        private void Completed(Task t)
        {
            //获取完数据，一定要关闭IO
            //此处如果通讯失败，可在PLC中关闭，以防万一
            ScrewDownStop();
            ushort v = 31;
            var opData = _mdRtu.ReadInt16((int)ScrewAdr.TASK_ID + "", v);
            if (opData.IsSuccess)
                CompletedDataEvent?.Invoke(opData.Content);

            CompletedEvent?.Invoke();
        }
        private Dictionary<short, string> dicError
        {
            get
            {
                return new Dictionary<short, string>
                {
                    { 1,"浮高"},
                    { 2,"滑牙"},
                    { 3,"过流"},
                    { 4,"过压"},
                    { 5,"欠压"},
                    { 6,"飞车"},
                    { 7,"I2T过热"},
                    { 8,"反转不到位"},
                    { 9,"位置偏差过大"},
                    { 10,"电批断线"},
                    { 11,"力矩偏差过大"},
                    { 12,"拧松失败"},
                    { 32,"试用超时"},
                };
            }
        }
        public bool getResult(short result, short errorId, out string errorMsg)
        {
            errorMsg = "";
            if (result == 19387)
            {
                return true;
            }
            if (result == 19660)
            {
                errorMsg = "未完成";
            }
            if (dicError.ContainsKey(errorId))
                errorMsg = dicError[errorId];

            return false;
        }
        #region 电批动作
        /// <summary>
        /// 螺丝拧紧启动
        /// </summary>
        private bool ScrewDownStart()
        {
            short v = (short)ScrewAction.开始拧紧;
            var opBytes = _mdRtu.Write((int)ScrewAdr.ACTIVE + "", v);
            if (!opBytes.IsSuccess)
                ErrorEvent?.Invoke($"电批启动【{opBytes.Message}】");

            return opBytes.IsSuccess;
        }
        /// <summary>
        /// 螺丝反松启动
        /// </summary>
        private bool ScrewUpStart()
        {
            var opBytes = _mdRtu.Write((int)ScrewAdr.ACTIVE + "", (short)ScrewAction.开始拧松);
            return opBytes.IsSuccess;
        }
        /// <summary>
        /// 螺丝拧紧启动
        /// </summary>
        private bool ScrewDownStop()
        {
            var opBytes = _mdRtu.Write((int)ScrewAdr.ACTIVE + "", (short)ScrewAction.停止拧紧);
            return opBytes.IsSuccess;
        }
        /// <summary>
        /// 螺丝反松启动
        /// </summary>
        private bool ScrewUpStop()
        {
            var opBytes = _mdRtu.Write((int)ScrewAdr.ACTIVE + "", (short)ScrewAction.停止拧松);
            return opBytes.IsSuccess;
        }
        /// <summary>
        /// 切换电批控制方式
        /// </summary>
        /// <param name="type"></param>
        public bool ChangeControlType(ScrewCommType type)
        {
            var opBytes = _mdRtu.Write((int)ScrewAdr.CONTROL_TYPE + "", (short)type);
            if (opBytes.IsSuccess)
                commType = type;

            return opBytes.IsSuccess;
        }
        /// <summary>
        /// 切换任务编号
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool ChnageTask(short id)
        {
            var opBytes = _mdRtu.Write((int)ScrewAdr.TASK_ID_CHANGE + "", id);
            return opBytes.IsSuccess;
        }
        #endregion

        #region IO操作
        private bool IoState_In(int index)
        {
            return IoState((int)ScrewAdr.IN_PORT + "", index);
        }
        private bool IoState_Out(int index)
        {
            return IoState((int)ScrewAdr.OUT_PORT + "", index);
        }
        private bool IoState(string adr, int index)
        {
            bool result = false;
            var opBytes = _mdRtu.Read(adr, 1);
            if (opBytes.IsSuccess)
            {
                try
                {
                    var data = Convert.ToString(opBytes.Content[1], 2).Reverse().ToList();
                    result = data[index] == '1';
                }
                catch (System.Exception ex)
                {
                    ErrorEvent?.Invoke(ex.Message);
                }
            }
            else
            {
                ErrorEvent?.Invoke(opBytes.Message);
            }
            return result;
        }
        #endregion

        ~ScrewHelper()
        {

        }
    }
    /// <summary>
    /// 电批通讯类型
    /// </summary>
    public enum ScrewCommType
    {
        IO = 0x3AAA,
        PC = 0x3BBB
    }
    public enum ScrewAction
    {
        开始拧紧 = 0x2AAA,
        停止拧紧 = 0x2BBB,
        开始拧松 = 0x2CCC,
        停止拧松 = 0x2DDD,
        开始自由转 = 0x2EEE,
        停止自由转 = 0x2FFF
    }
    public enum ScrewAdr
    {
        [Description("通道1")]
        CHN1_ENABLED = 61301,
        [Description("通道2")]
        CHN2_ENABLED = 61302,
        [Description("数据点数")]
        POINT_DATA = 60635,
        [Description("任务号")]
        TASK_ID = 60625,
        [Description("输入点")]
        IN_PORT = 60640,
        [Description("输出点")]
        OUT_PORT = 60642,
        [Description("任务切换")]
        TASK_ID_CHANGE = 3901,
        [Description("控制方式")]
        CONTROL_TYPE = 61506,
        [Description("动作")]
        ACTIVE = 61500,
    }
}
